在 xv6 Shell 按下 Ctrl+P,終端機會列出目前的行程。
其中有正在等待輸入的 Shell,也有開機時建立的 init,這些名字背後都對應到核心中的一格 struct proc。
今天從這張表出發,追到行程剛建立的那一刻,看看 PID、核心堆疊與頁表何時準備好。
本文沿用 Day 19 固定的 xv6 commit 9e3161a9abf5f51ea402562d1874caf6c4926597,後續改動會在同一份開發分支累積。
《Operating System Concepts》把行程控制區塊(Process Control Block,PCB)用來組織作業系統管理行程所需的狀態。
在 xv6 中,對應的結構是 kernel/proc.h 裡的 struct proc,但內容不會與教科書示意圖逐欄相同。
| 欄位 | 回答的問題 |
|---|---|
pid、state |
這是誰,目前能否執行? |
context |
下次恢復核心執行時,從哪裡繼續? |
trapframe |
使用者程式被中斷時的暫存器在哪裡? |
pagetable、sz |
使用者位址空間如何組成? |
kstack |
這個行程進入核心時使用哪一份堆疊? |
ofile、cwd |
開啟的檔案與目前目錄是什麼? |
context 與 trapframe 最容易混淆。
前者支援核心上下文切換,後者保存使用者 Trap 的現場,不能因為兩者都有暫存器就合併成一個概念。
在 30-days-os-kernel/examples/xv6-riscv/ 執行:
git status --short
git switch -c kernel-observer
make TOOLPREFIX=riscv64-linux-gnu- CPUS=1 qemu
若分支已存在,直接使用自己的開發分支,不必重建。
啟動後按 Ctrl+P,再執行 echo pcb-ready,最後再按一次 Ctrl+P。
短命的 echo 可能已退出,因此不一定出現在第二份列表,這正好提醒我們:列表是一個觀察時點,不是歷史紀錄。
原本的 procdump() 為了方便卡死時除錯,刻意不取得所有行程鎖。
因此它適合作為診斷畫面,不應被宣稱為多核心下的一致性快照。
先結束 QEMU,在 kernel/proc.c 的 allocproc() 定義前加入:
static void
obs_created(struct proc *p)
{
if (!holding(&p->lock))
panic("obs_created lock");
printk("create pid=%d state=%d kstack=%p pagetable=%p\n",
p->pid, p->state, (void *)p->kstack, (void *)p->pagetable);
}
在 allocproc() 最後,設定完 p->context.sp 之後、成功的 return p; 之前呼叫:
obs_created(p);
這個位置已成功配置 Trap Frame 與頁表,而且仍持有 p->lock。
不要放在每個失敗返回之前,也不要將尚未初始化的指標拿來輸出。
重新執行 make TOOLPREFIX=riscv64-linux-gnu- CPUS=1 qemu,再於 xv6 Shell 輸入 echo pcb-ready。
應看見新的 create 訊息,位址請以自己的建置結果為準。
固定版本的 allocproc() 先把狀態設成 USED,代表這格已被占用,但還沒完成成為可執行行程的全部準備。
建立者還要填入使用者映像或複製父行程資源,之後才改成 RUNNABLE。
同樣地,剛建立的頁表不表示使用者程式已完整載入。
看到非零指標,只證明有一份頁表結構,不能據此推論 text、stack 都已存在。
沿著 kexit()、kwait() 與 freeproc() 閱讀,行程結束後會先保留 ZOMBIE 狀態,讓父行程取得結束結果,再回收可重用的行程表位置。
這與 Day 11 的 DONE 有相似目的,但 xv6 還必須處理父子關係與等待。
核心堆疊也不是在每次 allocproc() 都重新配置。
本版本的 proc_mapstacks() 在初始化時替行程表各格建立核心堆疊映射,後續會重用對應的位置。
所以先後兩個不同 PID 出現相同 kstack,不一定是錯誤,可能是同一格被再次使用。
proc.h 的註解把欄位分成不同保護範圍。
部分欄位需要 p->lock,父子關係由 wait_lock 協調,另一些則由目前行程自己管理。
不能把「我已取得 p->lock」當成可以任意讀寫其他行程所有欄位的通行證。
這也是後面設計統計 API 時要先決定的事:究竟讀取自己的資料,還是想取得全系統的一致快照?
兩者需要的同步設計不同。
本日修改 kernel/proc.c,加入建立成功的診斷 helper,不改變原本行程生命週期。
這個開機診斷只供前期檢查,Day 29 整合時會移除,避免干擾統計測試。
建議 commit:
day21: inspect xv6 process allocation and lifecycle
下一篇沿著 context 走進 swtch.S,看看行程資料如何真的接手 CPU。